local _G, event, table, pairs, print, tostring
    = _G, event, table, pairs, print, tostring

module 'TCPEventHandler'

local REQUESTS = {} -- objetos que solicitam conexão
local CONNECTIONS = {} -- tabela que armazena todas as conexões ativas

-- contador de conexões
local id = 1

-- função auxiliar utilizada para controlar os objetos que solicitam conexão
local function get_object_id(host, port)
    for i, v in pairs(REQUESTS) do
        if v.host == host and v.port == port then
            return i
        end
    end
end

-- 'método' de conexão em um objeto de conexão: armazena o objeto na tabela de solicitações e lança um evento de conexão
local function connect(self)
    if not self.connection then
        REQUESTS[tostring(id)] = self
        id = id + 1
        event.post {class = 'tcp', type = 'connect', host = self.host, port  = self.port}
    end
end

-- 'método' de envio de dados em um objeto de conexão: se a conexão estiver ativa, envia um evento de dados, do contrário, armazena os dados no buffer e solicita conexão
local function send(self, data)
        if self.connection then
            event.post {class = 'tcp', type = 'data', connection = self.connection, value = data}
    else
        table.insert(self.buffer, data)
        self:connect()
    end
end

-- 'método' de desconexão em um objeto de conexão: envia um evento de desconexão
local function diconnect(self)
    if self.connection then
        event.post {class = 'tcp', type = 'diconnect', connection = self.connection}
    end
end


-- 'método' de tratamento de sucesso de conexão em um objeto de conexão: envia os dados armazenados no buffer e delega o tratamento de sucesso na conexão ao tratador da conexão
local function on_connect(self)
    while self.buffer[1] do
        event.post {class = 'tcp', type = 'data', connection = self.connection, value = self.buffer[1]}
        table.remove(self.buffer, 1)
    end
    self.buffer = {}
    if self.handler.on_connect then
        self.handler:on_connect()
    end
end


-- 'método' de tratamento de dados em um objeto de conexão: delega o tratamento de dados ao tratador da conexão
local function on_receive(self, data)
    if self.handler.on_receive then
        self.handler:on_receive(data)
    end
end

-- 'método' de tratamento de desconexão em um objeto de conexão: delega o tratamento de desconexão ao tratador da conexão
local function on_disconnect(self)
    if self.handler.on_disconnect then
        self.handler:on_disconnect()
    end
end

-- 'método' de tratamento de falhas em um objeto de conexão: delega o tratamento de falhas ao tratador da conexão
local function on_error(self, error)
    if self.handler.on_error then
        self.handler:on_error(error)
    end
end

-- construtor de um objeto de conexão
function Connection(host, port, handler)
   local obj = {
                -- propriedades da conexão
                handler = handler,
                host = host,
                port = port,
                connection = nil,
                buffer = {}, 

                -- envio de eventos
                connect = connect,
                send = send,
                disconnect = disconnect,

                -- tratadores de eventos
                on_connect = on_connect,
                on_receive = on_receive, 
                on_disconnect = on_disconnect,       
                on_error = on_error
               }

   -- associa a conexão ao tratador de eventos
   handler['connection'] = obj
   return obj
end


-- define uma função tratadora de eventos da classe 'tcp'
function handler (evt)

    if evt.class ~= 'tcp' then
        return
    end

    -- ao receber eventos do tipo 'connect', seleciona o objeto que solicitou a conexão e invoca o método de tratamento (de sucesso na conexão ou falha). No caso de sucesso o objeto é cadastrado como uma conexão ativa
    if evt.type == 'connect' then
        local obj_id = get_object_id(evt.host, evt.port)
        local obj = REQUESTS[obj_id]
        REQUESTS[obj_id] = nil
        if not evt.error then
            obj.connection = evt.connection
            CONNECTIONS[evt.connection] = obj
            obj:on_connect()
        else
            obj:on_error(evt.error)
        end
    end

    -- ao receber eventos do tipo 'data', seleciona o objeto de conexão correspondente e invoca o método de tratamento (de dados recebidos ou de falha na comunicação)
    if evt.type == 'data' then        
        local obj = CONNECTIONS[evt.connection]
        if obj then
            if not evt.error then
                obj:on_receive(evt.value)
            else
                obj:on_error(evt.error)
            end            
        end
    end
    
    -- ao receber eventos do tipo 'disconnect', seleciona o objeto de conexão correspondente, retira este objeto da listra de conexões e invoca o método de tratamento de desconexão
    if evt.type == 'disconnect' then
        local obj = CONNECTIONS[evt.connection]
        if obj then
            obj.connection = nil
            CONNECTIONS[evt.connection] = nil
            obj:on_disconnect()
        end
    end
end

-- registra o tratador de eventos no módulo event
event.register(handler)
